package com.societyGames.codeExercise
{
import com.societyGames.action.ActionQueue;
import com.societyGames.codeAlong.userUtility.reportException;
import com.societyGames.system.EventObject;

import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.TextEvent;

public class CodeExercise extends Sprite implements ICodeExercise
{
  private static var _instance:CodeExercise;

  protected var _actionQueue:ActionQueue = new ActionQueue();
  protected var _lastError:Error;

  private var _hasUserCodeEnded:Boolean = false;
  private var _hasUserCodeCompleted:Boolean = false;
  private var _hasEvaluated:Boolean = false;
  private var _successEventObject:EventObject;
  private var _failureEventObject:EventObject;
  private var _errorEventObject:EventObject;
  private var _unhandledErrorEventObject:EventObject;
  private var _outputEventObject:EventObject;

  public function CodeExercise()
  {
    //Static init
    _instance = this; //This only works because each code exercise is loaded into its own ApplicationDomain.

    //EventObjects:
    this._successEventObject = new EventObject(this, "SUCCESS");
    this._failureEventObject = new EventObject(this, "FAILURE");
    this._errorEventObject = new EventObject(this, "ERROR"); //ErrorEvent
    this._unhandledErrorEventObject = new EventObject(this, "UNHANDLED_ERROR_EVENT");
    this._outputEventObject = new EventObject(this, "OUTPUT"); //TextEvent

    //Action queue.
    this._actionQueue.emptyEventObject.addEventListener(onActionQueueEmpty, false, 0, true);

    //Shared functions:
    var reportExceptionFunction:Function = reportException; //Include the function.
    var outputFunction:Function = output;
    var endFunction:Function = __end__;
  }

  public static function get instance():CodeExercise
  {
    return _instance;
  }

  public function reportSuccess():void
  {
    this._successEventObject.dispatchEvent();
  }

  public function reportFailure():void
  {
    this._failureEventObject.dispatchEvent();
  }

  public function queueReportException(error:Error):void
  {
    this._lastError = error;
  }

  public function queueOutput(...arguments):void
  {
    var text:String = createOutputText(arguments);
    this._actionQueue.add(new DispatchEventObjectAction(this._outputEventObject, new TextEvent(this._outputEventObject.type, false, false, text)));
  }

  protected function createOutputText(items:Array):String
  {
    var text:String = "";
    for each (var object:Object in arguments)
    {
      var objectText:String;
      if (object != null)
      {
        objectText = object.toString();
      }
      else
      {
        objectText = "null";
      }
      if (text.length > 0)
      {
        text += " " + objectText;
      }
      else
      {
        text += objectText;
      }
    }
    return text;
  }

  private function onActionQueueEmpty(event:Event):void
  {
    evaluate();
  }

  private function evaluate():void
  {
    /*if (_hasEvaluated)
    {
      throw new Error("Double evaluation!");
    }*/
    if (this._actionQueue.isEmpty && _hasUserCodeEnded && !_hasEvaluated)
    {
      _hasEvaluated = true;
      if (this._lastError != null) //We have a known error. Show that.
      {
        var errorEvent:ErrorEvent = new ErrorEvent(this._errorEventObject.type, this._lastError, false, false);
        this._errorEventObject.dispatchEvent(errorEvent);
      }
      else if (_hasUserCodeCompleted) //We ended, completed, and without known errors.
      {
        evaluateWithExceptionsHandled();
      }
      else //User code ended but did not complete, probably because of unhandled exception.
      {
        this._unhandledErrorEventObject.dispatchEvent();
      }
    }
  }

  //Called by Executor when the user's code has stopped running.
  final public function handleUserCodeEnd():void //Override this function if we need different exception handling.
  {
    _hasUserCodeEnded = true;
    evaluate();
  }

  //ICodeExercise-->
  public function initialize():void
  {
    //Override
  }

  public function set maximumActionCount(value:uint):void
  {
    _actionQueue.maximumCount = value;
  }

  //Called when the CodeExercise should evaluate, assuming exceptions were already
  //handled.
  protected function evaluateWithExceptionsHandled():void
  {
    //override
  }

  public function stop():void
  {
    this._actionQueue.stop();
  }

  //Called when the user's code hits completion: No unhandled exceptions.
  public function handleUserCodeComplete():void
  {
    _hasUserCodeCompleted = true;
    //We don't evaluate here because we know handleUserCodeEnded must get called later.
    //That will trigger an evaluate.
  }

  public function get successEventObject():EventObject
  {
    return this._successEventObject;
  }

  public function get failureEventObject():EventObject
  {
    return this._failureEventObject;
  }

  public function get errorEventObject():EventObject 	//ErrorEvent. Received error.
  {
    return this._errorEventObject;
  }

  public function get unhandledErrorEventObject():EventObject //Event. Some error that nobody caught so we don't know what it is.
  {
    return this._unhandledErrorEventObject;
  }

  public function get outputEventObject():EventObject 	//TextEvent. Display output.
  {
    return this._outputEventObject;
  }

  public function get displayObject():DisplayObject
  {
    return this;
  }

  //<--ICodeExercise
}
}